using System;
using System.Collections.Generic;
using System.Text;
using WMEncoderLib;
using Microsoft.Build.Utilities;
using System.Threading;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace WMVConvert
{
	internal class Encoder: IEncoder, IDisposable
	{
		IMediaInfo item;
		IEncoderConfig config;
		string destination;
		IWMEncoder2 objWMEncoder;
		IWMEncProfile2 wmeProfile;
		bool valid;
		static Dictionary<string, WMENC_VIDEO_OPTIMIZATION> videoOptimizations;

		private TaskLoggingHelper log;

		private TaskLoggingHelper Log
		{
			get { return log; }
		}

		public Encoder(string destination, IMediaInfo item, IEncoderConfig config,TaskLoggingHelper log)
		{
			this.log = log;
			this.config = config;
			this.destination = destination;
			this.item = item;
			this.objWMEncoder = new WMEncoderClass();
			this.wmeProfile = null;
			this.valid = false;
			if (videoOptimizations == null)
			{
				videoOptimizations = new Dictionary<string, WMENC_VIDEO_OPTIMIZATION>();
			}
		}

		public WMENC_INDEXER_STATE IndexerState
		{
			get
			{
				return this.objWMEncoder.IndexerState;
			}
		}

		private bool HasVideo
		{
			get
			{
				Debug.Assert(wmeProfile!=null);
				return ((wmeProfile.ContentType & Constants.WMENC_CONTENT_ONE_VIDEO) == Constants.WMENC_CONTENT_ONE_VIDEO);
			}
		}

		private bool HasAudio
		{
			get
			{
				Debug.Assert(wmeProfile!=null);
				return ((wmeProfile.ContentType & Constants.WMENC_CONTENT_ONE_AUDIO) == Constants.WMENC_CONTENT_ONE_AUDIO);
			}
		}

		private bool Init(IProfile profile)
		{
			try
			{
				this.objWMEncoder.Reset();
				this.wmeProfile = profile.WMEProfile;
			}
			catch (Exception exception)
			{
				Log.LogError(exception.Message);
				return false;
			}
			IWMEncSourceGroupCollection sourceGroupCollection = this.objWMEncoder.SourceGroupCollection;
			IWMEncSourceGroup group = null;
			try
			{
				group = sourceGroupCollection.Add("WMVConvert");
			}
			catch (Exception exception)
			{
				Log.LogError(exception.Message);
				return false;
			}
			#region Set Inputs
			IWMEncVideoSource2 videoSource = null;
			IWMEncAudioSource audioSource= null;
			try
			{
				if (!HasAudio && !HasVideo)
				{
					throw new Exception("Profile not supported");
				}
				if (HasVideo)
				{
					videoSource = (IWMEncVideoSource2)group.AddSource(WMENC_SOURCE_TYPE.WMENC_VIDEO);
					videoSource.SetInput(item.SourceFile, string.Empty, string.Empty);
					SetMarks(videoSource);
				}
				if (HasAudio)
				{
					audioSource = (IWMEncAudioSource)group.AddSource(WMENC_SOURCE_TYPE.WMENC_AUDIO);
					audioSource.SetInput(item.SourceFile, string.Empty, string.Empty);
					SetMarks(audioSource);
				}
			}
			catch (Exception exception)
			{
				Log.LogMessage("MarkIn={0}", MarkIn);
				Log.LogMessage("MarkOut={0}", MarkOut);
				Log.LogError(exception.Message);
				return false;
			}
			#endregion
			try
			{
				group.set_Profile(wmeProfile);
				sourceGroupCollection.Active = group;
			}
			catch (Exception exception)
			{
				Log.LogError(exception.Message);
				return false;
			}

			if (this.config.VideoPreset != null)
			{
				this.objWMEncoder.VideoComplexity = this.config.VideoPreset.VideoPerformance;
				this.config.VideoPreset.Set();
			}

			#region Set Metadata properties
			IWMEncDisplayInfo displayInfo = this.objWMEncoder.DisplayInfo;
			try
			{
				if (!string.IsNullOrEmpty(item.Title))
				{
					displayInfo.Title = item.Title;
				}
				if (!string.IsNullOrEmpty(item.Description))
				{
					displayInfo.Description = item.Description;
				}
				if (!string.IsNullOrEmpty(item.Author))
				{
					displayInfo.Author = item.Author;
				}
				if (!string.IsNullOrEmpty(item.Copyright))
				{
					displayInfo.Copyright = item.Copyright;
				}
			}
			catch (Exception exception)
			{
				Log.LogWarning(exception.Message);
			}
			#endregion
			#region Manage Cropping
			try
			{
				if (HasVideo && Crop)
				{
					videoSource.CroppingBottomMargin = (int)this.CropBottom;
					videoSource.CroppingTopMargin = (int)this.CropTop;
					videoSource.CroppingLeftMargin = (int)this.CropLeft;
					videoSource.CroppingRightMargin = (int)this.CropRight;
				}
			}
			catch (Exception exception)
			{
				Log.LogWarning(exception.Message);
			}
			#endregion
			#region Video Optimization
			try
			{
				if (!videoOptimizations.ContainsKey(item.SourceFile))
				{
					IWMEncContentAnalyzer contentAnalyzer = this.objWMEncoder.ContentAnalyzer;
					contentAnalyzer.SourceGroup = sourceGroupCollection.Item(0);
					contentAnalyzer.Start();
					Thread.Sleep(4000);
					WMENC_VIDEO_OPTIMIZATION optimization = (WMENC_VIDEO_OPTIMIZATION)(contentAnalyzer.Property(WMENC_SOURCE_TYPE.WMENC_VIDEO, 0, "Optimization"));
					videoSource.Optimization = optimization;
					contentAnalyzer.Stop();
					videoOptimizations.Add(item.SourceFile, optimization);
				}
				else
				{
					WMENC_VIDEO_OPTIMIZATION optimization = videoOptimizations[item.SourceFile];
					videoSource.Optimization = optimization;
				}
			}
			catch (Exception exception)
			{
				Log.LogMessage(exception.Message);
				return false;
			}
			#endregion
			try
			{
				if (HasAudio)
				{
					if (!this.TwoPass)
					{
						audioSource.PreProcessPass = 0;
					}
					else
					{
						WMENC_PROFILE_VBR_MODE audioVBRMode = wmeProfile.get_VBRMode(WMENC_SOURCE_TYPE.WMENC_AUDIO, 0);
						switch (audioVBRMode)
						{
							case WMENC_PROFILE_VBR_MODE.WMENC_PVM_NONE:
							case WMENC_PROFILE_VBR_MODE.WMENC_PVM_PEAK:
							case WMENC_PROFILE_VBR_MODE.WMENC_PVM_BITRATE_BASED:
								audioSource.PreProcessPass = 1;
								break;
							case WMENC_PROFILE_VBR_MODE.WMENC_PVM_UNCONSTRAINED:
								audioSource.PreProcessPass = 0;
								break;
						}
					}
				}
				if (HasVideo)
				{
					if (!this.TwoPass)
					{
						videoSource.PreProcessPass = 0;
					}
					else
					{
						WMENC_PROFILE_VBR_MODE videoVBRMode = wmeProfile.get_VBRMode(WMENC_SOURCE_TYPE.WMENC_VIDEO, 0);
						switch (videoVBRMode)
						{
							case WMENC_PROFILE_VBR_MODE.WMENC_PVM_NONE:
							case WMENC_PROFILE_VBR_MODE.WMENC_PVM_PEAK:
							case WMENC_PROFILE_VBR_MODE.WMENC_PVM_BITRATE_BASED:
								videoSource.PreProcessPass = 1;
								break;
							case WMENC_PROFILE_VBR_MODE.WMENC_PVM_UNCONSTRAINED:
								videoSource.PreProcessPass = 0;
								break;
						}
					}
				}
			}
			catch (Exception exception)
			{
				Log.LogError(exception.Message);
				return false;
			}
			IWMEncFile2 file = (IWMEncFile2)this.objWMEncoder.File;
			try
			{
				file.LocalFileName = this.destination;
			}
			catch (Exception exception)
			{
				Log.LogError(exception.Message);
				return false;
			}
			try
			{
				this.objWMEncoder.RemoteAdmin = false;
				this.objWMEncoder.AutoIndex = false;
				this.objWMEncoder.PrepareToEncode(true);
			}
			catch (Exception exception)
			{
				Log.LogError(exception.Message);
				return false;
			}
			return true;
		}

		private void SetMarks(IWMEncSource source)
		{
			source.MarkIn = MarkIn;
			/*
			if (MarkOut < item.PlayTime)
			{
				source.MarkOut = MarkOut;
			}*/
		}

		public int Duration
		{
			get
			{
				return MarkOut - MarkIn;
			}
		}

		private int MarkIn
		{
			get
			{
				return Math.Max(item.Start, 0);
			}
		}

		private int MarkOut
		{
			get
			{
				return Math.Min(item.End + config.SegmentDelta, (int)item.PlayTime);
			}
		}

		private bool Crop
		{
			get { return false; }
		}

		private long CropLeft
		{
			get { return 0; }
		}

		private long CropTop
		{
			get { return 0; }
		}

		private long CropRight
		{
			get { return 0; }
		}

		private long CropBottom
		{
			get { return 0; }
		}

		private bool TwoPass
		{
			get { return false; }
		}


		#region IEncoder Members

		public bool IsValid
		{
			get { return valid; }
		}

		public void Reset(IProfile profile)
		{
			try
			{
				valid = Init(profile);
			}
			catch
			{
				valid = false;
			}
		}

		public WMENC_ENCODER_STATE State
		{
			get { return this.objWMEncoder.RunState; }
		}

		public void Start()
		{
			this.objWMEncoder.Start();
		}

		public void Stop()
		{
			this.objWMEncoder.Stop();
		}

		public double Progress
		{
			get
			{
				IWMEncFileArchiveStats fileArchiveStats = (IWMEncFileArchiveStats)this.objWMEncoder.Statistics.FileArchiveStats;
				double currentDuration = (double)(fileArchiveStats.FileDuration * (new Decimal(10000)));
				double totalDuration = MarkOut - MarkIn;
				if (item.PlayTime <= config.SegmentSize )
				{
					totalDuration = item.PlayTime;
				}
				Debug.Assert(totalDuration > 0);
				double progress = currentDuration / totalDuration;
				return progress;
			}
		}

		public bool OkToCut
		{
			get
			{
				IWMEncFileArchiveStats fileArchiveStats = (IWMEncFileArchiveStats)this.objWMEncoder.Statistics.FileArchiveStats;
				long currentDuration = (long)(fileArchiveStats.FileDuration * (new Decimal(10000)));
				currentDuration += MarkIn;
				long delta = MarkOut - currentDuration;
				return delta <= 2000;
			}
		}

		#endregion

		#region Implement IDisposable

		bool disposed;

		// Do not make this method virtual.
		// A derived class should not be able to override this method.
		public void Dispose()
		{
			Dispose(true);
			// Take yourself off the Finalization queue 
			// to prevent finalization code for this object
			// from executing a second time.
			GC.SuppressFinalize(this);
		}

		// Dispose(bool disposing) executes in two distinct scenarios.
		// If disposing equals true, the method has been called directly
		// or indirectly by a user's code. Managed and unmanaged resources
		// can be disposed.
		// If disposing equals false, the method has been called by the 
		// runtime from inside the finalizer and you should not reference 
		// other objects. Only unmanaged resources can be disposed.
		protected virtual void Dispose(bool disposing)
		{
			// Check to see if Dispose has already been called.
			if (!this.disposed)
			{
				// If disposing equals true, dispose all managed 
				// and unmanaged resources.
				if (disposing)
				{
					// Dispose managed resources.
				}
				// Release unmanaged resources. If disposing is false, 
				// only the following code is executed.
				this.objWMEncoder.Reset();
				Marshal.ReleaseComObject(this.objWMEncoder);
				this.objWMEncoder = null;

				// Note that this is not thread safe.
				// Another thread could start disposing the object
				// after the managed resources are disposed,
				// but before the disposed flag is set to true.
				// If thread safety is necessary, it must be
				// implemented by the client.

			}
			disposed = true;
		}

		// Use C# destructor syntax for finalization code.
		// This destructor will run only if the Dispose method 
		// does not get called.
		// It gives your base class the opportunity to finalize.
		// Do not provide destructors in types derived from this class.
		~Encoder()
		{
			// Do not re-create Dispose clean-up code here.
			// Calling Dispose(false) is optimal in terms of
			// readability and maintainability.
			Dispose(false);
		}

		#endregion
	}
}
